
// Copyright (C) 2009-2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;

using Derm.Render;
using Derm.Xml;

namespace Derm.Scene
{
	/// <summary>
	/// Hierarchy dedicated for rendering.
	/// </summary>
	/// <remarks>
	/// <para>
	/// For rendering complex scenes is usually used a directed graph, allowing to order the
	/// rendering operations in a proper way. The directed graph nodes represent a group of
	/// rendering operations, and the connection between the graph nodes specify a set of
	/// ordering of these operations.
	/// </para>
	/// <para>
	/// The relationship between graph nodes is defined in RenderGraphNode: the local model
	/// dependency is propagated to the nodes linked to the current node.
	/// </para>
	/// <para>
	/// The possible rendering operations are classified in:
	/// - Rendering operations: this kind of operations allow to draw polygons on surfaces
	/// - Selecting operations: this kind of operations allow to select rendering operations
	///   based on the result of rendering
	/// </para>
	/// </remarks>
	public class RenderHierarchy : DirectedGraph<RenderGraphNode>, IXmlSerializableExt
	{
		#region Constructors

		/// <summary>
		/// Construct a RenderHierarchy.
		/// </summary>
		public RenderHierarchy()
		{
			RootNode = new TransformGraphNode();
		}

		#endregion

		#region Graph Node Operations

		/// <summary>
		/// Delegate used for filtering 
		/// </summary>
		/// <param name="graphNode"></param>
		/// <returns></returns>
		public delegate bool FilterNodeDelegate(RenderGraphNode graphNode);

		/// <summary>
		/// Find a single node in this render graph.
		/// </summary>
		/// <param name="filter">
		/// A <see cref="FilterNodeDelegate"/> that specify the filter criteria.
		/// </param>
		/// <returns>
		/// It returns the first node filtered by <paramref name="filter"/>.
		/// </returns>
		public RenderGraphNode FindNode(FilterNodeDelegate filter)
		{
			TraversalContext ctx = new TraversalContext();
			RenderGraphNode filteredNode = null;

			// Define graph traverse
			ctx.VisitorDelegate = delegate(RenderGraphNode node, object data)
          	{
				if ((filteredNode == null) && (filter(node) == true))
					filteredNode = node;
          	};
			// Traverse graph
			TraverseParentFirst(ctx, null);

			return (filteredNode);
		}

		/// <summary>
		/// Find a collection of nodes in this render graph.
		/// </summary>
		/// <param name="filter">
		/// A <see cref="FilterNodeDelegate"/> that specify the filter criteria.
		/// </param>
		/// <returns>
		/// It returns the first node filtered by <paramref name="filter"/>.
		/// </returns>
		public IList<RenderGraphNode> FindNodes(FilterNodeDelegate filter)
		{
			TraversalContext ctx = new TraversalContext();
			List<RenderGraphNode> filteredNodes = new List<RenderGraphNode>();

			// Define graph traverse
			ctx.VisitorDelegate = delegate(RenderGraphNode node, object data)
          	{
				if (filter(node) == true)
					filteredNodes.Add(node);
          	};
			// Traverse graph
			TraverseParentFirst(ctx, null);

			return (filteredNodes);
		}

		/// <summary>
		/// Find a collection of nodes in this graph filtering by node type.
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		public IList<RenderGraphNode> FindNodes(RenderGraphNodeType type)
		{
			return (FindNodes(delegate(RenderGraphNode node) { return (node.NodeType == type); }));
		}

		#endregion

		#region Scene State

		private LightingState GetSceneLighing()
		{
			LightingState sceneLighting = new LightingState();

			sceneLighting.Lights.Clear();

			foreach (RenderGraphNode graphNode in FindNodes(RenderGraphNodeType.LightNode)) {
				LightGraphNode lightNode = (LightGraphNode) graphNode;
				Vertex3f viewPos, forwardVector;

				

				// View matrix
				RootNode.LocalModel.GetTranslate(out viewPos);
				// View direction
				forwardVector = -RootNode.LocalModel.ForwardVector;

				if (lightNode.AssociatedLight is AmbientLight)
					sceneLighting.AmbientLighting = ((AmbientLight) lightNode.AssociatedLight).AmbientColor.Red;

				// Update view-dependent variables
				lightNode.AssociatedLight.UpdateViewState(lightNode.WorldViewModel, viewPos, forwardVector);

				// Collect light for shading
				sceneLighting.Lights.Add(lightNode.AssociatedLight);
			}

			return (sceneLighting);
		}

		#endregion

		#region RenderHierarchy Operations

		/// <summary>
		/// Render graph nodes.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for rendering.
		/// </param>
		public void Render(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			// Reset context state to default
			ctx.ResetState();

			// Update lighting state
			RootNode.State.DefineState(GetSceneLighing());
			// Start rendering from the root node
			RootNode.Render(ctx);
		}

		/// <summary>
		/// Pick graph nodes.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for picking.
		/// </param>
		public void Pick(RenderContext ctx)
		{
			RenderGraphNode root = RootNode;

			// Start rendering from the root node
			
		}
		
		#endregion

		#region IXmlSerializableExt Implementation

		/// <summary>
		/// This property is reserved, apply the System.Xml.Serialization.XmlSchemaProviderAttribute to the class instead.
		/// </summary>
		/// <returns>
		/// An System.Xml.Schema.XmlSchema that describes the XML representation of the object that is produced by the
		/// System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter) method and consumed by the
		/// System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader) method.
		/// </returns>
		public XmlSchema GetSchema() { return (null); }

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="xmlReader">
		/// The System.Xml.XmlReader stream from which the object is deserialized.
		/// </param>
		public void ReadXml(XmlReader xmlReader)
		{
			RootNode = new TransformGraphNode("Root");
			RootNode.Link(XmlHelper.ReadXmlDynamicElementList<RenderGraphNode>(xmlReader));
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">
		/// The System.Xml.XmlWriter stream to which the object is serialized.
		/// </param>
		public void WriteXml(XmlWriter writer)
		{
			
		}

		/// <summary>
		/// Read the attribute of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading element attributes.
		/// </param>
		public void ReadAttributes(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");
		}

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element.
		/// </param>
		public void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");
		}

		/// <summary>
		/// Read the text of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		public void ReadText(XmlReader xmlReader)
		{
			
		}

		#endregion
	}
}